#include	"DBProxyService.h"

#include	<protocols/DBProxy.pb.h>
#include	<IniFile.h>

namespace {
	struct IBaseTask : public SQL::ITask {
		uint64_t from;
		uint32_t serial;

		IBaseTask(uint64_t from, uint32_t serial)
			: from(from)
			, serial(serial) {
		}

		void Send(int pf, const google::protobuf::Message & msg) {
			auto sender = DBProxyService::Instance().Find(from);
			if (sender) sender->Reply(pf, msg, serial, true);
		}
	};

	struct RoleListTask : public IBaseTask {		
		std::string account;
		uint32_t sid;
		uint64_t at;

		RoleListTask(uint64_t sender, uint32_t seq, Proto::DBProxy::ReqRoleList * req)
			: IBaseTask(sender, seq)
			, account(req->account())
			, sid(req->sid())
			, at(req->at()) {}

		virtual void OnRun(SQL::Database & db) override {
			auto cursor = db.Query("SELECT * from `roledata` WHERE account='" + account + "' AND sid=" + std::to_string(sid));

			Proto::DBProxy::RspRoleList rsp;
			rsp.set_account(account);
			rsp.set_at(at);
			while (cursor->Next()) rsp.add_roles()->CopyFrom(cursor->Cast<Core::RolePreviewData>());

			Send(Proto::RSP_ROLELIST, rsp);
		}
	};

	struct RoleTask : public IBaseTask {
		std::string account;
		uint64_t pid;
		uint64_t at;

		RoleTask(uint64_t sender, uint32_t seq, Proto::DBProxy::ReqRole * req)
			: IBaseTask(sender, seq)
			, account(req->account())
			, pid(req->pid())
			, at(req->at()) {}

		virtual void OnRun(SQL::Database & db) override {
			auto cursor = db.Query("SELECT * from `roledata` WHERE account='" + account + "' AND pid=" + std::to_string(pid));

			Proto::DBProxy::RspRole rsp;
			rsp.set_account(account);
			rsp.set_at(at);
			
			if (cursor->Next()) {
				rsp.set_status(Proto::S_Success);
				rsp.mutable_role()->CopyFrom(cursor->Cast<Core::RoleData>());
			} else {
				rsp.set_status(Proto::S_ENotFound);
			}

			Send(Proto::RSP_GETROLE, rsp);
		}
	};

	struct MailTask : public IBaseTask {
		uint64_t pid;

		MailTask(uint64_t sender, uint32_t seq, Proto::DBProxy::ReqMail * req)
			: IBaseTask(sender, seq)
			, pid(req->pid()) {}

		virtual void OnRun(SQL::Database & db) override {
			auto cursor = db.Query("SELECT * FROM `maildata` WHERE is_readed=0 AND pid=" + std::to_string(pid));

			Proto::DBProxy::RspMail rsp;
			rsp.set_pid(pid);
			while (cursor->Next()) rsp.add_mails()->CopyFrom(cursor->Cast<Core::MailData>());

			if (rsp.mails_size() > 0) Send(Proto::RSP_GETMAIL, rsp);
		}
	};
}

void DBProxyService::OnReceive(Sender * sender, Protocol * msg) {
#define PUSH_JOB(TASK, PROTO) { \
	auto req = Cast<PROTO>(msg); \
	if (req) _queue.Push<TASK>(sender->Id(), msg->serial, req); \
}

	switch (msg->pf) {
	case Proto::REQ_ROLELIST: PUSH_JOB(RoleListTask, Proto::DBProxy::ReqRoleList); break;
	case Proto::REQ_GETROLE: PUSH_JOB(RoleTask, Proto::DBProxy::ReqRole); break;
	case Proto::REQ_GETMAIL: PUSH_JOB(MailTask, Proto::DBProxy::ReqMail); break;
	default:
		LOG_ERR("[dbproxy]Unsupported protocol received.");
		break;
	}

#undef PUSH_JOB
}

bool DBProxyService::Initialize(class IniFile & conf) {
	std::string host = conf.Get<std::string>("mysql", "ip", "127.0.0.1");
	int port = conf.Get<int>("mysql", "port", 3306);
	std::string user = conf.Get<std::string>("mysql", "user", "root");
	std::string pswd = conf.Get<std::string>("mysql", "pswd", "root");
	std::string db = conf.Get<std::string>("mysql", "db", "game");

	if (!_queue.Open(host, port, user, pswd, db)) return false;

	std::string listen_ip = conf.Get<std::string>("dbproxy", "ip", "127.0.0.1");
	int listen_port = conf.Get<int>("dbproxy", "port", 10000);
	if (Listen(listen_ip, listen_port) != ENet::Success) {
		LOG_ERR("Failed to listen on %s:%d", listen_ip.c_str(), listen_port);
		return false;
	}

	LOG_INFO("Application dbproxy started ...");
	return true;
}